package log

import (
	"fmt"
	"gitee.com/xluomo945/fm/helper"
	"github.com/gin-gonic/gin"
	"gorm.io/driver/sqlite"
	"gorm.io/gorm"
	"os"
	"sort"
	"strings"
	"time"
)

type LogsServer struct {
	flags  map[string]func(*value, string)
	self   string
	detail string
}

func NewLogsServer(selfRoute string) *LogsServer {
	ss := &LogsServer{self: selfRoute}
	ss.detail = fmt.Sprintf("%s/detail", ss.self)
	ss.flags = map[string]func(*value, string){
		startTime: ss.start,
		endTime:   ss.end,
		useTime:   ss.use,
		route:     ss.route,
		url:       ss.url,
		method:    ss.method,
		httpCode:  ss.code,
		header:    ss.header,
		request:   ss.request,
		response:  ss.response,
	}
	return ss
}

const (
	startTime = "start time"
	endTime   = "end time"
	useTime   = "use time"
	route     = "route"
	method    = "method"
	httpCode  = "http code"
	header    = "header"
	request   = "data"
	response  = "response"
	url       = "url"
)

func (l *LogsServer) start(data *value, msg string) {
	data.StartTime = l.msg(msg, "start time")
}
func (l *LogsServer) end(data *value, msg string) {
	data.EndTime = l.msg(msg, "end time")
}
func (l *LogsServer) use(data *value, msg string) {
	data.UseTime = l.msg(msg, "use time")
}
func (l *LogsServer) route(data *value, msg string) {
	data.Route = l.msg(msg, "route")
}
func (l *LogsServer) method(data *value, msg string) {
	data.Method = l.msg(msg, "method")
}
func (l *LogsServer) code(data *value, msg string) {
	data.Code = l.msg(msg, "http code")
}
func (l *LogsServer) header(data *value, msg string) {
	data.Header = l.msg(msg, "header")
}
func (l *LogsServer) request(data *value, msg string) {
	data.Request = l.msg(msg, "data")
}
func (l *LogsServer) response(data *value, msg string) {
	data.Response = l.msg(msg, "response")
}

func (l *LogsServer) url(data *value, msg string) {
	data.Url = l.msg(msg, "url")
}

type LogsShowRequest struct {
	Date  string `uri:"date"`
	Trace string `form:"trace"`
	All   string `form:"all"`
}

type value struct {
	Trace     string
	Route     string
	Url       string
	Code      string
	Method    string
	StartTime string
	EndTime   string
	UseTime   string
	Time      string
	Header    string
	Request   string
	Response  string
	Level     string
	Msg       []string
}

type s struct {
	list []*value
}

func (s s) Len() int {
	return len(s.list)
}

func (s s) Less(i, j int) bool {
	t1, _ := time.Parse("2006-01-02 15:04:05", s.list[i].StartTime)
	t2, _ := time.Parse("2006-01-02 15:04:05", s.list[j].StartTime)
	return t1.After(t2)
}

func (s s) Swap(i, j int) {
	s.list[i], s.list[j] = s.list[j], s.list[i]
}

func (l *LogsServer) dto(ctx *gin.Context) *LogsShowRequest {
	data := &LogsShowRequest{}
	_ = ctx.ShouldBindUri(data)
	t, e := time.Parse("20060102", data.Date)
	if e == nil {
		data.Date = t.Format("2006-01-02")
	}
	data.Trace = ctx.Query("trace")
	data.All = ctx.Query("all")
	return data
}

func (l *LogsServer) db(dto *LogsShowRequest) *gorm.DB {
	filename := customName(dto.Date)
	_, isErr := os.Stat(filename)
	if isErr != nil {
		return nil
	}

	logDB, err := gorm.Open(sqlite.Open(filename), &gorm.Config{})
	if err != nil {
		return nil
	}
	return logDB
}

func (l *LogsServer) Show(ctx *gin.Context) {

	data := l.dto(ctx)
	logDB := l.db(data)
	if logDB == nil {
		return
	}

	var logs []map[string]interface{}
	var total int64
	err := logDB.Table("FM_LOGS").Find(&logs).Error
	countErr := logDB.Table("FM_LOGS").Count(&total).Error
	if err != nil || countErr != nil {
		return
	}

	var resp = make(map[string]*value)
	for k := range logs {
		v := resp[logs[k]["trace"].(string)]
		if v == nil {
			v = &value{
				Trace: logs[k]["trace"].(string),
				Level: logs[k]["level"].(string),
				Time:  logs[k]["datetime"].(time.Time).Format("2006-04-02 15:04:05"),
			}
		}
		level := logs[k]["level"].(string)
		msg := logs[k]["msg"].(string)
		l.parse(v, msg, level, []string{httpCode, route, url, method, startTime, endTime, useTime})
		resp[logs[k]["trace"].(string)] = v
	}

	var list []*value
	for k := range resp {
		if helper.StringEmpty(data.All) && (resp[k].Route == l.self || resp[k].Route == l.detail) {
			continue
		}
		resp[k].Code = helper.StringDefault(resp[k].Code, "500")
		resp[k].Route = helper.StringDefault(resp[k].Route, "no route")
		list = append(list, resp[k])
	}

	result := &s{list: list}

	sort.Sort(result)

	ctx.HTML(200, "runtime.tmpl", gin.H{
		"list":  result.list,
		"total": total,
	})
}

func (l *LogsServer) parse(data *value, msg string, level string, flags []string) bool {
	for k := range flags {
		if l.flag(msg, flags[k]) {
			if fn, ok := l.flags[flags[k]]; ok && l.flag(msg, flags[k]) {
				fn(data, msg)
				return true
			}
		}
	}
	data.Msg = append(data.Msg, fmt.Sprintf("%s => %s", level, msg))
	return false
}

func (l *LogsServer) msg(msg string, flag string) string {
	return strings.TrimSpace(msg[len(flag)+1:])
}

func (l *LogsServer) flag(msg string, flag string) bool {
	if len(msg) > 20 {
		return strings.Contains(msg[0:20], fmt.Sprintf("%s:", flag))
	}
	return strings.Contains(msg, fmt.Sprintf("%s:", flag))
}

func (l *LogsServer) Filter(ctx *gin.Context) {
	data := l.dto(ctx)
	logDB := l.db(data)
	if logDB == nil {
		return
	}

	var logs []map[string]interface{}
	var total int64
	err := logDB.Table("FM_LOGS").Where("trace = ?", data.Trace).Find(&logs).Error
	countErr := logDB.Table("FM_LOGS").Where("trace = ?", data.Trace).Count(&total).Error
	if err != nil || countErr != nil {
		return
	}

	var resp = make(map[string]*value)
	for k := range logs {
		v := resp[logs[k]["trace"].(string)]
		if v == nil {
			v = &value{
				Trace: logs[k]["trace"].(string),
				Level: logs[k]["level"].(string),
				Time:  logs[k]["datetime"].(time.Time).Format("2006-04-02 15:04:05"),
			}
		}
		level := logs[k]["level"].(string)
		msg := logs[k]["msg"].(string)
		l.parse(v, msg, level, []string{httpCode, route, url, method, startTime, endTime, useTime, header, request, response})
		resp[logs[k]["trace"].(string)] = v
	}

	var list []*value
	for k := range resp {
		resp[k].Code = helper.StringDefault(resp[k].Code, "500")
		resp[k].Route = helper.StringDefault(resp[k].Route, "no route")
		list = append(list, resp[k])
	}

	result := &s{list: list}

	sort.Sort(result)

	ctx.HTML(200, "detail.tmpl", gin.H{
		"list":  result.list,
		"total": total,
	})
}
